Open In Colab

ImageStacking šŸ“šĀ¶

This notebook aims to explore the purpose of Image Stacking and observe how capturing multiple short exposures is advantegeous than one single exposure for Astrophotography.

Author: Timothy Do

References¶

[1] PhotographingSpace.com. (2023, December 22). Homework: Download and Stack my Data! [Online]. Available: https://www.photographingspace.com/homework-download-stack-data/
[2] Ethan Rublee, Vincent Rabaud, Kurt Konolige, and Gary Bradski. ORB: an efficient alternative to SIFT or SURF. In 2011 IEEE International Conference on Computer Vision (ICCV), 1 pages 2564–2571. IEEE, 2011.

Dependencies¶

InĀ [1]:
IN_COLAB = True
try: 
    import google.colab
except: 
    IN_COLAB = False
print(f'In CoLab: {IN_COLAB}')
In CoLab: False
InĀ [2]:
if(IN_COLAB):
    !curl https://raw.githubusercontent.com/dotimothy/astronomy/main/requirements.txt -o ./requirements.txt
    !pip install -r requirements.txt
else:
    !pip install -r ../requirements.txt
Requirement already satisfied: opencv-python in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 2)) (4.10.0.84)
Requirement already satisfied: pillow in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 3)) (11.0.0)
Requirement already satisfied: exifread in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 4)) (3.0.0)
Requirement already satisfied: matplotlib in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 5)) (3.9.2)
Requirement already satisfied: numpy in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 6)) (2.0.2)
Requirement already satisfied: rawpy in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 7)) (0.24.0)
Requirement already satisfied: tqdm in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 8)) (4.67.0)
Requirement already satisfied: jupyter in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 9)) (1.1.1)
Requirement already satisfied: torch in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 10)) (2.5.1)
Requirement already satisfied: h5py in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 11)) (3.12.1)
Requirement already satisfied: pandas in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 12)) (2.2.3)
Requirement already satisfied: xarray in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 13)) (2024.7.0)
Requirement already satisfied: earthaccess in /Users/tim/env/lib/python3.9/site-packages (from -r ../requirements.txt (line 14)) (0.11.0)
Requirement already satisfied: contourpy>=1.0.1 in /Users/tim/env/lib/python3.9/site-packages (from matplotlib->-r ../requirements.txt (line 5)) (1.3.0)
Requirement already satisfied: cycler>=0.10 in /Users/tim/env/lib/python3.9/site-packages (from matplotlib->-r ../requirements.txt (line 5)) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /Users/tim/env/lib/python3.9/site-packages (from matplotlib->-r ../requirements.txt (line 5)) (4.55.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /Users/tim/env/lib/python3.9/site-packages (from matplotlib->-r ../requirements.txt (line 5)) (1.4.7)
Requirement already satisfied: packaging>=20.0 in /Users/tim/env/lib/python3.9/site-packages (from matplotlib->-r ../requirements.txt (line 5)) (24.2)
Requirement already satisfied: pyparsing>=2.3.1 in /Users/tim/env/lib/python3.9/site-packages (from matplotlib->-r ../requirements.txt (line 5)) (3.2.0)
Requirement already satisfied: python-dateutil>=2.7 in /Users/tim/env/lib/python3.9/site-packages (from matplotlib->-r ../requirements.txt (line 5)) (2.9.0.post0)
Requirement already satisfied: importlib-resources>=3.2.0 in /Users/tim/env/lib/python3.9/site-packages (from matplotlib->-r ../requirements.txt (line 5)) (6.4.5)
Requirement already satisfied: notebook in /Users/tim/env/lib/python3.9/site-packages (from jupyter->-r ../requirements.txt (line 9)) (7.2.2)
Requirement already satisfied: jupyter-console in /Users/tim/env/lib/python3.9/site-packages (from jupyter->-r ../requirements.txt (line 9)) (6.6.3)
Requirement already satisfied: nbconvert in /Users/tim/env/lib/python3.9/site-packages (from jupyter->-r ../requirements.txt (line 9)) (7.16.4)
Requirement already satisfied: ipykernel in /Users/tim/env/lib/python3.9/site-packages (from jupyter->-r ../requirements.txt (line 9)) (6.29.5)
Requirement already satisfied: ipywidgets in /Users/tim/env/lib/python3.9/site-packages (from jupyter->-r ../requirements.txt (line 9)) (8.1.5)
Requirement already satisfied: jupyterlab in /Users/tim/env/lib/python3.9/site-packages (from jupyter->-r ../requirements.txt (line 9)) (4.2.6)
Requirement already satisfied: filelock in /Users/tim/env/lib/python3.9/site-packages (from torch->-r ../requirements.txt (line 10)) (3.16.1)
Requirement already satisfied: typing-extensions>=4.8.0 in /Users/tim/env/lib/python3.9/site-packages (from torch->-r ../requirements.txt (line 10)) (4.12.2)
Requirement already satisfied: networkx in /Users/tim/env/lib/python3.9/site-packages (from torch->-r ../requirements.txt (line 10)) (3.2.1)
Requirement already satisfied: jinja2 in /Users/tim/env/lib/python3.9/site-packages (from torch->-r ../requirements.txt (line 10)) (3.1.4)
Requirement already satisfied: fsspec in /Users/tim/env/lib/python3.9/site-packages (from torch->-r ../requirements.txt (line 10)) (2024.12.0)
Requirement already satisfied: sympy==1.13.1 in /Users/tim/env/lib/python3.9/site-packages (from torch->-r ../requirements.txt (line 10)) (1.13.1)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /Users/tim/env/lib/python3.9/site-packages (from sympy==1.13.1->torch->-r ../requirements.txt (line 10)) (1.3.0)
Requirement already satisfied: pytz>=2020.1 in /Users/tim/env/lib/python3.9/site-packages (from pandas->-r ../requirements.txt (line 12)) (2024.2)
Requirement already satisfied: tzdata>=2022.7 in /Users/tim/env/lib/python3.9/site-packages (from pandas->-r ../requirements.txt (line 12)) (2024.2)
Requirement already satisfied: multimethod>=1.8 in /Users/tim/env/lib/python3.9/site-packages (from earthaccess->-r ../requirements.txt (line 14)) (1.12)
Requirement already satisfied: pqdm>=0.1 in /Users/tim/env/lib/python3.9/site-packages (from earthaccess->-r ../requirements.txt (line 14)) (0.2.0)
Requirement already satisfied: python-cmr>=0.10.0 in /Users/tim/env/lib/python3.9/site-packages (from earthaccess->-r ../requirements.txt (line 14)) (0.13.0)
Requirement already satisfied: requests>=2.26 in /Users/tim/env/lib/python3.9/site-packages (from earthaccess->-r ../requirements.txt (line 14)) (2.32.3)
Requirement already satisfied: s3fs>=2022.11 in /Users/tim/env/lib/python3.9/site-packages (from earthaccess->-r ../requirements.txt (line 14)) (2024.12.0)
Requirement already satisfied: tinynetrc>=1.3.1 in /Users/tim/env/lib/python3.9/site-packages (from earthaccess->-r ../requirements.txt (line 14)) (1.3.1)
Requirement already satisfied: zipp>=3.1.0 in /Users/tim/env/lib/python3.9/site-packages (from importlib-resources>=3.2.0->matplotlib->-r ../requirements.txt (line 5)) (3.21.0)
Requirement already satisfied: bounded-pool-executor in /Users/tim/env/lib/python3.9/site-packages (from pqdm>=0.1->earthaccess->-r ../requirements.txt (line 14)) (0.0.3)
Requirement already satisfied: six>=1.5 in /Users/tim/env/lib/python3.9/site-packages (from python-dateutil>=2.7->matplotlib->-r ../requirements.txt (line 5)) (1.16.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /Users/tim/env/lib/python3.9/site-packages (from requests>=2.26->earthaccess->-r ../requirements.txt (line 14)) (3.4.0)
Requirement already satisfied: idna<4,>=2.5 in /Users/tim/env/lib/python3.9/site-packages (from requests>=2.26->earthaccess->-r ../requirements.txt (line 14)) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/tim/env/lib/python3.9/site-packages (from requests>=2.26->earthaccess->-r ../requirements.txt (line 14)) (1.26.20)
Requirement already satisfied: certifi>=2017.4.17 in /Users/tim/env/lib/python3.9/site-packages (from requests>=2.26->earthaccess->-r ../requirements.txt (line 14)) (2024.8.30)
Requirement already satisfied: aiobotocore<3.0.0,>=2.5.4 in /Users/tim/env/lib/python3.9/site-packages (from s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (2.16.0)
Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /Users/tim/env/lib/python3.9/site-packages (from s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (3.11.11)
Requirement already satisfied: appnope in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (0.1.4)
Requirement already satisfied: comm>=0.1.1 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (0.2.2)
Requirement already satisfied: debugpy>=1.6.5 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (1.8.8)
Requirement already satisfied: ipython>=7.23.1 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (8.18.1)
Requirement already satisfied: jupyter-client>=6.1.12 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (8.6.3)
Requirement already satisfied: jupyter-core!=5.0.*,>=4.12 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (5.7.2)
Requirement already satisfied: matplotlib-inline>=0.1 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (0.1.7)
Requirement already satisfied: nest-asyncio in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (1.6.0)
Requirement already satisfied: psutil in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (6.1.0)
Requirement already satisfied: pyzmq>=24 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (26.2.0)
Requirement already satisfied: tornado>=6.1 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (6.4.1)
Requirement already satisfied: traitlets>=5.4.0 in /Users/tim/env/lib/python3.9/site-packages (from ipykernel->jupyter->-r ../requirements.txt (line 9)) (5.14.3)
Requirement already satisfied: widgetsnbextension~=4.0.12 in /Users/tim/env/lib/python3.9/site-packages (from ipywidgets->jupyter->-r ../requirements.txt (line 9)) (4.0.13)
Requirement already satisfied: jupyterlab-widgets~=3.0.12 in /Users/tim/env/lib/python3.9/site-packages (from ipywidgets->jupyter->-r ../requirements.txt (line 9)) (3.0.13)
Requirement already satisfied: MarkupSafe>=2.0 in /Users/tim/env/lib/python3.9/site-packages (from jinja2->torch->-r ../requirements.txt (line 10)) (2.1.5)
Requirement already satisfied: prompt-toolkit>=3.0.30 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-console->jupyter->-r ../requirements.txt (line 9)) (3.0.48)
Requirement already satisfied: pygments in /Users/tim/env/lib/python3.9/site-packages (from jupyter-console->jupyter->-r ../requirements.txt (line 9)) (2.18.0)
Requirement already satisfied: async-lru>=1.0.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.0.4)
Requirement already satisfied: httpx>=0.25.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.27.2)
Requirement already satisfied: importlib-metadata>=4.8.3 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (8.5.0)
Requirement already satisfied: jupyter-lsp>=2.0.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.2.5)
Requirement already satisfied: jupyter-server<3,>=2.4.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.14.2)
Requirement already satisfied: jupyterlab-server<3,>=2.27.1 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.27.3)
Requirement already satisfied: notebook-shim>=0.2 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.2.4)
Requirement already satisfied: setuptools>=40.1.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (74.1.2)
Requirement already satisfied: tomli>=1.2.2 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.1.0)
Requirement already satisfied: beautifulsoup4 in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (4.12.3)
Requirement already satisfied: bleach!=5.0.0 in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (6.2.0)
Requirement already satisfied: defusedxml in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (0.7.1)
Requirement already satisfied: jupyterlab-pygments in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (0.3.0)
Requirement already satisfied: mistune<4,>=2.0.3 in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (3.0.2)
Requirement already satisfied: nbclient>=0.5.0 in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (0.10.0)
Requirement already satisfied: nbformat>=5.7 in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (5.10.4)
Requirement already satisfied: pandocfilters>=1.4.1 in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (1.5.1)
Requirement already satisfied: tinycss2 in /Users/tim/env/lib/python3.9/site-packages (from nbconvert->jupyter->-r ../requirements.txt (line 9)) (1.4.0)
Requirement already satisfied: botocore<1.35.82,>=1.35.74 in /Users/tim/env/lib/python3.9/site-packages (from aiobotocore<3.0.0,>=2.5.4->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (1.35.81)
Requirement already satisfied: wrapt<2.0.0,>=1.10.10 in /Users/tim/env/lib/python3.9/site-packages (from aiobotocore<3.0.0,>=2.5.4->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (1.17.0)
Requirement already satisfied: aioitertools<1.0.0,>=0.5.1 in /Users/tim/env/lib/python3.9/site-packages (from aiobotocore<3.0.0,>=2.5.4->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (0.12.0)
Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /Users/tim/env/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (2.4.4)
Requirement already satisfied: aiosignal>=1.1.2 in /Users/tim/env/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (1.3.2)
Requirement already satisfied: async-timeout<6.0,>=4.0 in /Users/tim/env/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (5.0.1)
Requirement already satisfied: attrs>=17.3.0 in /Users/tim/env/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (24.2.0)
Requirement already satisfied: frozenlist>=1.1.1 in /Users/tim/env/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (1.5.0)
Requirement already satisfied: multidict<7.0,>=4.5 in /Users/tim/env/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (6.1.0)
Requirement already satisfied: propcache>=0.2.0 in /Users/tim/env/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (0.2.1)
Requirement already satisfied: yarl<2.0,>=1.17.0 in /Users/tim/env/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (1.18.3)
Requirement already satisfied: webencodings in /Users/tim/env/lib/python3.9/site-packages (from bleach!=5.0.0->nbconvert->jupyter->-r ../requirements.txt (line 9)) (0.5.1)
Requirement already satisfied: anyio in /Users/tim/env/lib/python3.9/site-packages (from httpx>=0.25.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (4.6.2.post1)
Requirement already satisfied: httpcore==1.* in /Users/tim/env/lib/python3.9/site-packages (from httpx>=0.25.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (1.0.7)
Requirement already satisfied: sniffio in /Users/tim/env/lib/python3.9/site-packages (from httpx>=0.25.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (1.3.1)
Requirement already satisfied: h11<0.15,>=0.13 in /Users/tim/env/lib/python3.9/site-packages (from httpcore==1.*->httpx>=0.25.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.14.0)
Requirement already satisfied: decorator in /Users/tim/env/lib/python3.9/site-packages (from ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (5.1.1)
Requirement already satisfied: jedi>=0.16 in /Users/tim/env/lib/python3.9/site-packages (from ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (0.19.2)
Requirement already satisfied: stack-data in /Users/tim/env/lib/python3.9/site-packages (from ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (0.6.3)
Requirement already satisfied: exceptiongroup in /Users/tim/env/lib/python3.9/site-packages (from ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (1.2.2)
Requirement already satisfied: pexpect>4.3 in /Users/tim/env/lib/python3.9/site-packages (from ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (4.9.0)
Requirement already satisfied: platformdirs>=2.5 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-core!=5.0.*,>=4.12->ipykernel->jupyter->-r ../requirements.txt (line 9)) (4.3.6)
Requirement already satisfied: argon2-cffi>=21.1 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (23.1.0)
Requirement already satisfied: jupyter-events>=0.9.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.10.0)
Requirement already satisfied: jupyter-server-terminals>=0.4.4 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.5.3)
Requirement already satisfied: overrides>=5.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (7.7.0)
Requirement already satisfied: prometheus-client>=0.9 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.21.0)
Requirement already satisfied: send2trash>=1.8.2 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (1.8.3)
Requirement already satisfied: terminado>=0.8.3 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.18.1)
Requirement already satisfied: websocket-client>=1.7 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (1.8.0)
Requirement already satisfied: babel>=2.10 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab-server<3,>=2.27.1->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.16.0)
Requirement already satisfied: json5>=0.9.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab-server<3,>=2.27.1->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.9.28)
Requirement already satisfied: jsonschema>=4.18.0 in /Users/tim/env/lib/python3.9/site-packages (from jupyterlab-server<3,>=2.27.1->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (4.23.0)
Requirement already satisfied: fastjsonschema>=2.15 in /Users/tim/env/lib/python3.9/site-packages (from nbformat>=5.7->nbconvert->jupyter->-r ../requirements.txt (line 9)) (2.20.0)
Requirement already satisfied: wcwidth in /Users/tim/env/lib/python3.9/site-packages (from prompt-toolkit>=3.0.30->jupyter-console->jupyter->-r ../requirements.txt (line 9)) (0.2.13)
Requirement already satisfied: soupsieve>1.2 in /Users/tim/env/lib/python3.9/site-packages (from beautifulsoup4->nbconvert->jupyter->-r ../requirements.txt (line 9)) (2.6)
Requirement already satisfied: argon2-cffi-bindings in /Users/tim/env/lib/python3.9/site-packages (from argon2-cffi>=21.1->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (21.2.0)
Requirement already satisfied: jmespath<2.0.0,>=0.7.1 in /Users/tim/env/lib/python3.9/site-packages (from botocore<1.35.82,>=1.35.74->aiobotocore<3.0.0,>=2.5.4->s3fs>=2022.11->earthaccess->-r ../requirements.txt (line 14)) (1.0.1)
Requirement already satisfied: parso<0.9.0,>=0.8.4 in /Users/tim/env/lib/python3.9/site-packages (from jedi>=0.16->ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (0.8.4)
Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /Users/tim/env/lib/python3.9/site-packages (from jsonschema>=4.18.0->jupyterlab-server<3,>=2.27.1->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2024.10.1)
Requirement already satisfied: referencing>=0.28.4 in /Users/tim/env/lib/python3.9/site-packages (from jsonschema>=4.18.0->jupyterlab-server<3,>=2.27.1->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.35.1)
Requirement already satisfied: rpds-py>=0.7.1 in /Users/tim/env/lib/python3.9/site-packages (from jsonschema>=4.18.0->jupyterlab-server<3,>=2.27.1->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.21.0)
Requirement already satisfied: python-json-logger>=2.0.4 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.0.7)
Requirement already satisfied: pyyaml>=5.3 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (6.0.2)
Requirement already satisfied: rfc3339-validator in /Users/tim/env/lib/python3.9/site-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.1.4)
Requirement already satisfied: rfc3986-validator>=0.1.1 in /Users/tim/env/lib/python3.9/site-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (0.1.1)
Requirement already satisfied: ptyprocess>=0.5 in /Users/tim/env/lib/python3.9/site-packages (from pexpect>4.3->ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (0.7.0)
Requirement already satisfied: executing>=1.2.0 in /Users/tim/env/lib/python3.9/site-packages (from stack-data->ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (2.1.0)
Requirement already satisfied: asttokens>=2.1.0 in /Users/tim/env/lib/python3.9/site-packages (from stack-data->ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (2.4.1)
Requirement already satisfied: pure-eval in /Users/tim/env/lib/python3.9/site-packages (from stack-data->ipython>=7.23.1->ipykernel->jupyter->-r ../requirements.txt (line 9)) (0.2.3)
Requirement already satisfied: fqdn in /Users/tim/env/lib/python3.9/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (1.5.1)
Requirement already satisfied: isoduration in /Users/tim/env/lib/python3.9/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (20.11.0)
Requirement already satisfied: jsonpointer>1.13 in /Users/tim/env/lib/python3.9/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (3.0.0)
Requirement already satisfied: uri-template in /Users/tim/env/lib/python3.9/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (1.3.0)
Requirement already satisfied: webcolors>=24.6.0 in /Users/tim/env/lib/python3.9/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (24.11.1)
Requirement already satisfied: cffi>=1.0.1 in /Users/tim/env/lib/python3.9/site-packages (from argon2-cffi-bindings->argon2-cffi>=21.1->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (1.17.1)
Requirement already satisfied: pycparser in /Users/tim/env/lib/python3.9/site-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi>=21.1->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.22)
Requirement already satisfied: arrow>=0.15.0 in /Users/tim/env/lib/python3.9/site-packages (from isoduration->jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (1.3.0)
Requirement already satisfied: types-python-dateutil>=2.8.10 in /Users/tim/env/lib/python3.9/site-packages (from arrow>=0.15.0->isoduration->jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->jupyterlab->jupyter->-r ../requirements.txt (line 9)) (2.9.0.20241003)
InĀ [3]:
# Dependencies
import os
import numpy as np
import rawpy
import cv2 as cv
import matplotlib.pyplot as plt 
from tqdm import tqdm
InĀ [4]:
# Global Helper Functions
# Crudely Normalize an Image (float) from 0-1
def normalize(img):
    return (img - img.min())/(img.max()-img.min())

# Contrast Stretches an Image (float)
def contrast_stretch(img,min=0,max=1):
    return np.clip(normalize(img) * (max-min) + min,a_min=0,a_max=1)

# Convert a uint8 image to a float image
def int_to_float(img):
    return normalize(img.astype(np.float))

# Convert a float image to a uint8 image
def float_to_int(img):
    return (255.0*normalize(img)).astype(np.uint8)

# Converts CR2 to Numpy Array
def cr2_to_numpy(cr2Path,uint=False):
    with rawpy.imread(cr2Path) as raw:
        if(uint):
            out = raw.postprocess() # uint8
        else:
            out = raw.postprocess(use_camera_wb=True,half_size=False,output_bps=16,no_auto_bright=False)
            out = out.astype(np.float32)/ 2**16
    return out

Downloading the Data¶

For exploring the process of Image Stacking, we will be downloading a "Homework" example from Cory Schmitz's blog in the Photograph Space site! He takes a really nice shot of the milky way that we can work with. [1]

Here are some logisitcs of the stack, taken on a static tripod:

  • Camera: Canon 5D Mark III
  • ISO: 6400
  • Exposure Length: 15 seconds
  • White Balance: 4250K
InĀ [5]:
# Creating Directory to Store the Data
dataDir = '../data/imagestacking'
os.makedirs(dataDir,exist_ok=True)
InĀ [6]:
# Downloading the Single Raw Image & Full Stack
site = "https://www.photographingspace.com/downloads/"
singleRawFile = "CSM30803.CR2"
stackFile = "11x_ISO6400_f2.8_15s_5DMkIII_raw.zip"
for file in [singleRawFile,stackFile]: 
    filePath = f'{dataDir}/{file}'
    if(not(os.path.exists(filePath))):
        fileLink = f'{site}/{file}'
        os.system(f'curl {fileLink} -o {filePath}')
    if(file.endswith('.zip')):
        zipDir = filePath.split('.zip')[0]
        if(not(os.path.exists(zipDir))):
            os.system(f'unzip {filePath} -d {zipDir}')

Visualizing the Data¶

InĀ [26]:
# Extracting Single Raw Image & Viewing
singleRawPath = f'{dataDir}/{singleRawFile}'
singleRaw = cr2_to_numpy(singleRawPath)
print(f'Single Raw Numpy Shape: {singleRaw.shape}')
print(f'Single Raw Numpy Data Type: {singleRaw.dtype}')
cv.imwrite(f"{dataDir}/{singleRawFile.strip('.CR2')}.png",float_to_int(singleRaw)[...,::-1])

# Plotting
plt.figure(figsize=(15,10))
plt.title(f'Single Raw: {singleRawFile}')
plt.imshow(contrast_stretch(singleRaw))
plt.axis('off')
plt.show()
Single Raw Numpy Shape: (3870, 5796, 3)
Single Raw Numpy Data Type: float32
No description has been provided for this image
InĀ [8]:
# Read the Stacks into Memory
stackPath = f'{dataDir}/11x_ISO6400_f2.8_15s_5DMkIII_raw/raw'
stackFiles = sorted([file for file in os.listdir(stackPath) if file.endswith('.CR2')])
print(f'Stack Files: {stackFiles}')
print('Stacking Raw Images into a Tensor')
stackedNumpy = np.stack([cr2_to_numpy(f'{stackPath}/{stackFile}') for stackFile in tqdm(stackFiles)])
print(f'Stacked Numpy Shape: {stackedNumpy.shape}')
print(f'Stacked Numpy Data Type: {stackedNumpy.dtype}')
Stack Files: ['CSM30799.CR2', 'CSM30800.CR2', 'CSM30801.CR2', 'CSM30802.CR2', 'CSM30803.CR2', 'CSM30804.CR2', 'CSM30805.CR2', 'CSM30806.CR2', 'CSM30807.CR2', 'CSM30808.CR2', 'CSM30809.CR2']
Stacking Raw Images into a Tensor
100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 11/11 [00:08<00:00,  1.32it/s]
Stacked Numpy Shape: (11, 3870, 5796, 3)
Stacked Numpy Data Type: float32
InĀ [9]:
# Viewing the Stack
rows = 5
cols = 2
fig, ax = plt.subplots(nrows=rows,ncols=cols,figsize=(15,30))
plt.suptitle('Raw Image Stack')
for i in range(rows):
    for j in range(cols):
        ax[i][j].axis('off')
        ax[i][j].set_title(stackFiles[(i*cols)+j])
        ax[i][j].imshow(contrast_stretch(stackedNumpy[(i*cols)+j]))
plt.tight_layout()
plt.show()
No description has been provided for this image

Stacking Methods¶

We will explore stacking methods in order to reduce noise and have the deep sky objects really pop out! We first start off with Naive methods, which work by avergaing the images to filter noise. The noise is coming from the shot, coming from readout, thermal, etc. The noise can modeled as additive as shown below: $$I(x,y) = f(x,y) + n(x,y)$$ $$n(x,y) \sim \mathcal{N}(\mu,\sigma^2)$$, where it is assumed zero-mean and unit variance (i.e., $\mu = 0, \sigma = 1$).

Averaging works in the ideal setting (i.e., when there is no parallax between scenes), but not in the real world because of star trails. Star trails are caused when the stars rotate with the Earth's rotation in perspective of the shot, so we need to perform image registartion before stacking the images!

Simulation¶

We first provide an ideal scene, where a test scene is generated with a simulated deep sky object.

InĀ [10]:
# Helper Functions to Simulate Noise
# Creating Test Scene 
def createTestScene(width=64,height=64,radius=5):
    # Image dimensions
    # Create an empty image (black background)
    image = np.zeros((height, width,3))

    # Define circle parameters
    center_x = width // 2
    center_y = height // 2

    # Create a meshgrid
    y, x = np.ogrid[-center_y:height-center_y, -center_x:width-center_x]

    # Calculate distance from center
    dist_from_center = np.sqrt(x**2 + y**2)

    # Set pixels within the circle to white
    image[dist_from_center <= radius] = 1
    return image

# Genereates Noise with Zero Mean and Unit Variance 
def createNoise(width=64,height=64,dist='uniform'):
    if(dist == 'uniform'):
        noise = np.random.rand(height,width,3)
    elif(dist == 'gauss'):
        noise = np.random.randn(height,width,3)
    return normalize(noise)
InĀ [11]:
testScene = createTestScene()
print(f'Test Scene Shape: {testScene.shape}')
print(f'Test Scene Data Type: {testScene.dtype}')
plt.figure(figsize=(15,10))
plt.imshow(testScene)
plt.title('Test Scene')
plt.axis('off')
plt.show()
Test Scene Shape: (64, 64, 3)
Test Scene Data Type: float64
No description has been provided for this image
InĀ [12]:
testNoise = createNoise(dist='gauss')
print(f'Test Noise Shape: {testNoise.shape}')
print(f'Test Noise Data Type: {testNoise.dtype}')
print(f'Test Noise Min: {testNoise.min()}')
print(f'Test Noise Max: {testNoise.max()}')
plt.figure(figsize=(15,10))
plt.imshow(testNoise)
plt.title('Test Noise')
plt.axis('off')
plt.show()
Test Noise Shape: (64, 64, 3)
Test Noise Data Type: float64
Test Noise Min: 0.0
Test Noise Max: 1.0
No description has been provided for this image
InĀ [13]:
# Generate the Noisy Stack 
scale = 1
numStack = 2500
print('Creating Test Stack...')
testStack = np.stack([normalize(testScene + scale*createNoise(dist='uniform')) for _ in tqdm(range(numStack))])
print(f'Test Stack Shape: {testStack.shape}')
Creating Test Stack...
100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 2500/2500 [00:00<00:00, 20329.16it/s]
Test Stack Shape: (2500, 64, 64, 3)

InĀ [14]:
plt.figure(figsize=(15,10))
plt.title('Test Stack with Added Noise')
plt.axis('off')
plt.imshow(contrast_stretch(testStack[0]))
plt.show()
No description has been provided for this image
InĀ [15]:
# Viewing the Average Raw Image (Naive Stacking)
testMean = np.mean(testStack,axis=0)
# Plotting the Average
plt.figure(figsize=(15,10))
plt.title(f'Averaged Post-Processed Test Stack')
plt.imshow(contrast_stretch(testMean))
plt.axis('off')
plt.show()
No description has been provided for this image
InĀ [16]:
# Viewing the Median Raw Image (Naive Stacking)
testMedian = np.median(testStack,axis=0)
# Plotting the Average
plt.figure(figsize=(15,10))
plt.title(f'Median Post-Processed Test Stack')
plt.imshow(contrast_stretch(testMedian))
plt.axis('off')
plt.show()
No description has been provided for this image

Real Images¶

As seen above, averaging the noisy image stack elimiantes the zero mean noise, that's because as many samples, it models the distribution of the noise, which is $\sigma(0,1)$. However, the assumption is the scene is static, which is not the case for the Earth's constant rotation.

Every day, the Earth approximately rotates by 360 degrees, translating to 0.25 degrees a minute. Naively stacking all the images leads to translational error, visually seen in the form of star trails as shown below.

InĀ [17]:
# Viewing the Average Raw Image (Naive Stacking)
stackedMean = np.mean(stackedNumpy,axis=0)
# Plotting the Average
plt.figure(figsize=(15,10))
plt.title(f'Naive Stacking by Mean')
plt.imshow(contrast_stretch(stackedMean))
plt.axis('off')
plt.show()
No description has been provided for this image
InĀ [18]:
# Viewing the Median Raw Image (Naive Stacking)
stackedMedian = np.median(stackedNumpy,axis=0)
# Plotting the Median
plt.figure(figsize=(15,10))
plt.title(f'Naive Stacking by Median')
plt.imshow(contrast_stretch(stackedMedian))
plt.axis('off')
plt.show()
No description has been provided for this image

Image Registration¶

In order to correctly stack many astrophotography long exposures, a preprocessing operation of image registration needs to occur to align the images. ORB Image registration finds common keypoints between an image and a reference, estimates the transformation matrix, and maps the new pixels with respect to the reference to correct rotation [2]. This will mitigate the prescence of star trails and allow us to accurately stack our images for a nice celestial object!

InĀ [19]:
# Registers a Float Img (img2) with Respect to a Reference (img1)
def register_image(img1,img2,numFeatures=20000,match=0.95,refill=True):
    # Convert to uint8 grayscale.
    gray1 = cv.cvtColor(float_to_int(img1), cv.COLOR_BGR2GRAY)
    gray2 = cv.cvtColor(float_to_int(img2), cv.COLOR_BGR2GRAY)
    height, width = gray1.shape
    
    # Create ORB detector
    orb_detector = cv.ORB_create(numFeatures)
    
    # Find keypoints and descriptors.
    # The first arg is the image, second arg is the mask
    #  (which is not required in this case).
    kp1, d1 = orb_detector.detectAndCompute(gray1, None)
    kp2, d2 = orb_detector.detectAndCompute(gray2, None)
    
    # Match features between the two images.
    # We create a Brute Force matcher with 
    # Hamming distance as measurement mode.
    matcher = cv.BFMatcher(cv.NORM_HAMMING, crossCheck = True)
    
    # Match the two sets of descriptors.
    matches = list(matcher.match(d1, d2))

    
    # Sort matches on the basis of their Hamming distance.
    matches.sort(key = lambda x: x.distance)
    
    # Take the top matches forward.
    matches = matches[:int(len(matches)*match)]
    no_of_matches = len(matches)
    
    # Define empty matrices of shape no_of_matches * 2.
    p1 = np.zeros((no_of_matches, 2))
    p2 = np.zeros((no_of_matches, 2))
    
    for i in range(len(matches)):
      p1[i, :] = kp1[matches[i].queryIdx].pt
      p2[i, :] = kp2[matches[i].trainIdx].pt
    
    # Find the homography matrix.
    homography, mask = cv.findHomography(p1, p2, cv.RANSAC)
    
    # Use this matrix to transform the
    # colored image wrt the reference image.
    transformed_img = cv.warpPerspective(img1,
                        homography, (width, height))

    if(refill): 
        transformed_img = np.where(transformed_img != 0, transformed_img, img2) 
    
    return transformed_img

# Stacks Images with Automatic Alignment
def stack_images(imgStack,method='mean',align=True,refIndex=0.5,refill=True):
    if align:
        refIndex = len(imgStack) // 2 if refIndex == 0.5 else refIndex
        newImgs = []
        for i in tqdm(range(imgStack.shape[0])): 
            newImgs.append(normalize(imgStack[i]) if i == refIndex else register_image(imgStack[i],imgStack[refIndex],refill=refill))
        imgStack = np.stack(newImgs)
    stackedImg = np.median(imgStack,axis=0) if method == 'med' else np.mean(imgStack,axis=0)
    return stackedImg
InĀ [24]:
# Stacking the Astro Image
stackedImg = stack_images(stackedNumpy,refIndex=0)
100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 11/11 [00:16<00:00,  1.53s/it]
InĀ [25]:
# Plotting the Stacked Image
plt.figure(figsize=(15,10))
plt.title(f'Mean Stacking with ORB Alignment')
plt.imshow(contrast_stretch(stackedImg))
plt.axis('off')
plt.show()
No description has been provided for this image
InĀ [27]:
# Writing the Final Result
cv.imwrite(f'{dataDir}/stackedImg.png',float_to_int(stackedImg)[...,::-1])
Out[27]:
True

Now with Image Registration, we have succesfully merged our Astrophoto stack using Python! Feel free to apply any postprocessing tactics to make the images look nice!